#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
游戏手柄测试工具，用于测试手柄连接状态和按钮事件
"""

import sys
import time
import logging
import argparse
import json
import os
from pathlib import Path

# 添加项目根目录到路径
project_root = Path(__file__).parent.parent.absolute()
sys.path.insert(0, str(project_root))

try:
    import pygame
    from app.utils.joystick_detector import JoystickDetector
except ImportError as e:
    print(f"导入错误: {str(e)}")
    print("确保已安装pygame并且项目路径正确")
    sys.exit(1)

def setup_logging(verbose=False):
    """
    设置日志
    
    :param verbose: 是否显示详细日志
    """
    level = logging.DEBUG if verbose else logging.INFO
    logging.basicConfig(
        level=level,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    )

def load_button_names(mapping_file, joystick):
    """
    加载按钮名称映射
    
    :param mapping_file: 映射文件路径
    :param joystick: 游戏手柄对象
    :return: 按钮ID到名称的映射字典
    """
    button_names = {}
    
    # 先从文件加载自定义映射
    if mapping_file and os.path.exists(mapping_file):
        try:
            with open(mapping_file, 'r', encoding='utf-8') as f:
                mapping = json.load(f)
            
            # 将字符串键转换为整数键
            for key, value in mapping.items():
                try:
                    button_names[int(key)] = value
                except ValueError:
                    print(f"忽略无效的按钮ID: {key}")
            
            print(f"从 {mapping_file} 加载了 {len(button_names)} 个按钮名称")
        except Exception as e:
            print(f"加载映射文件失败: {str(e)}")
    
    # 如果没有完整的映射，为每个未映射的按钮生成一个默认名称
    num_buttons = joystick.get_numbuttons()
    detector = JoystickDetector()
    controller_name = joystick.get_name()
    
    for i in range(num_buttons):
        if i not in button_names:
            button_names[i] = detector.get_button_name(controller_name, i)
    
    return button_names

def test_joystick_events(joystick_id=0, duration=None, mapping_file=None):
    """
    测试游戏手柄的所有事件
    
    :param joystick_id: 游戏手柄ID
    :param duration: 测试持续时间（秒），None表示无限时间
    :param mapping_file: 按钮名称映射文件
    """
    # 初始化pygame
    pygame.init()
    pygame.joystick.init()
    
    # 检查游戏手柄是否可用
    joystick_count = pygame.joystick.get_count()
    if joystick_count == 0:
        print("未检测到游戏手柄设备")
        return 1
    
    if joystick_id >= joystick_count:
        print(f"游戏手柄ID {joystick_id} 超出范围，可用设备数量: {joystick_count}")
        return 1
    
    # 初始化游戏手柄
    joystick = pygame.joystick.Joystick(joystick_id)
    joystick.init()
    
    print(f"已连接到游戏手柄: {joystick.get_name()}")
    print(f"按钮数量: {joystick.get_numbuttons()}")
    print(f"轴数量: {joystick.get_numaxes()}")
    print(f"帽子数量: {joystick.get_numhats()}")
    
    # 加载按钮名称
    button_names = load_button_names(mapping_file, joystick)
    
    # 创建窗口显示事件（可选）
    if pygame.display.get_init():
        screen = pygame.display.set_mode((800, 600))  # 增大窗口尺寸
        pygame.display.set_caption("游戏手柄测试")
        # 使用系统字体以支持中文显示
        try:
            # 尝试使用系统中文字体
            system_fonts = pygame.font.get_fonts()
            # 优先尝试中文字体
            chinese_fonts = [
                "microsoftyahei", "msyh", "simsun", "simhei", "nsimsun", 
                "microsoft yahei", "dengxian", "fangsong", "kaiti",
                "arial unicode ms"
            ]
            font_name = None
            
            # 查找可用的中文字体
            for cf in chinese_fonts:
                if cf in system_fonts:
                    font_name = cf
                    break
            
            # 如果找到了中文字体，使用它
            if font_name:
                font = pygame.font.SysFont(font_name, 20)  # 调整字体大小
                title_font = pygame.font.SysFont(font_name, 24, bold=True)  # 标题字体
                print(f"使用系统字体: {font_name}")
            else:
                # 如果没有找到中文字体，使用默认字体
                font = pygame.font.Font(None, 20)
                title_font = pygame.font.Font(None, 24)
                print("警告: 未找到支持中文的系统字体，中文可能显示为乱码")
        except Exception as e:
            print(f"加载字体时出错: {str(e)}，使用默认字体")
            font = pygame.font.Font(None, 20)
            title_font = pygame.font.Font(None, 24)
    
    # 初始化状态存储
    buttons = [False] * joystick.get_numbuttons()
    axes = [0.0] * joystick.get_numaxes()
    hats = [(0, 0)] * joystick.get_numhats()
    
    # 记录开始时间
    start_time = time.time()
    
    # 主循环
    running = True
    
    print("\n开始监听游戏手柄事件，按Ctrl+C停止...")
    print("操作说明: 按下各个按钮以测试反应")
    
    try:
        while running:
            # 检查是否到达测试时间
            if duration and time.time() - start_time > duration:
                print(f"\n测试时间已到 ({duration} 秒)，退出...")
                break
            
            # 处理事件
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False
                
                # 按钮按下
                elif event.type == pygame.JOYBUTTONDOWN and event.joy == joystick_id:
                    button_id = event.button
                    buttons[button_id] = True
                    name = button_names.get(button_id, f"Button {button_id}")
                    print(f"按钮 {button_id} [{name}] 按下")
                
                # 按钮释放
                elif event.type == pygame.JOYBUTTONUP and event.joy == joystick_id:
                    button_id = event.button
                    buttons[button_id] = False
                    name = button_names.get(button_id, f"Button {button_id}")
                    print(f"按钮 {button_id} [{name}] 释放")
                
                # 轴移动
                elif event.type == pygame.JOYAXISMOTION and event.joy == joystick_id:
                    axis_id = event.axis
                    value = event.value
                    # 只在值变化超过阈值时显示
                    if abs(axes[axis_id] - value) > 0.1:
                        axes[axis_id] = value
                        print(f"轴 {axis_id} 值: {value:.2f}")
                
                # 帽子移动
                elif event.type == pygame.JOYHATMOTION and event.joy == joystick_id:
                    hat_id = event.hat
                    value = event.value
                    if hats[hat_id] != value:
                        hats[hat_id] = value
                        print(f"帽子 {hat_id} 值: {value}")
            
            # 更新显示（如果有窗口）
            if pygame.display.get_init():
                screen.fill((0, 0, 0))
                
                # 显示按钮状态
                y = 10
                title_text = title_font.render("按钮状态", True, (255, 255, 255))
                screen.blit(title_text, (10, y))
                y += 30
                
                for i, pressed in enumerate(buttons):
                    if i >= 15:  # 如果按钮太多，分两列显示
                        break
                    color = (0, 255, 0) if pressed else (255, 0, 0)
                    name = button_names.get(i, f"Button {i}")
                    text = font.render(f"按钮 {i} [{name}]: {'按下' if pressed else '释放'}", True, color)
                    screen.blit(text, (10, y))
                    y += 20
                
                # 如果按钮数量超过15个，分两列显示
                if len(buttons) > 15:
                    y = 40
                    for i in range(15, min(30, len(buttons))):
                        color = (0, 255, 0) if buttons[i] else (255, 0, 0)
                        name = button_names.get(i, f"Button {i}")
                        text = font.render(f"按钮 {i} [{name}]: {'按下' if buttons[i] else '释放'}", True, color)
                        screen.blit(text, (400, y))
                        y += 20
                
                # 显示轴状态
                y = 350
                title_text = title_font.render("轴状态", True, (255, 255, 255))
                screen.blit(title_text, (10, y))
                y += 30
                
                for i, value in enumerate(axes):
                    if i >= 6:  # 最多显示6个轴
                        break
                    # 计算颜色：从红(负值)到绿(正值)
                    if value < 0:
                        color = (255, int(255 * (1 + value)), 0)  # 红到黄
                    else:
                        color = (int(255 * (1 - value)), 255, 0)  # 黄到绿
                    
                    # 绘制轴值文本
                    text = font.render(f"轴 {i}: {value:.2f}", True, color)
                    screen.blit(text, (10, y))
                    
                    # 绘制轴值条
                    bar_x = 150
                    bar_y = y + 5
                    bar_width = int(100 * abs(value))
                    bar_height = 10
                    bar_x_offset = 0 if value >= 0 else -bar_width
                    pygame.draw.rect(screen, color, (bar_x + bar_x_offset, bar_y, bar_width, bar_height))
                    pygame.draw.rect(screen, (100, 100, 100), (bar_x - 100, bar_y, 200, bar_height), 1)
                    y += 25
                
                # 显示帽子状态
                y = 520
                title_text = title_font.render("帽子状态", True, (255, 255, 255))
                screen.blit(title_text, (10, y))
                y += 30
                
                for i, (x, y_hat) in enumerate(hats):
                    text = font.render(f"帽子 {i}: ({x}, {y_hat})", True, (255, 255, 255))
                    screen.blit(text, (10, y + i * 20))
                
                pygame.display.flip()
            
            # 控制循环速度
            time.sleep(0.01)
    
    except KeyboardInterrupt:
        print("\n用户中断，退出...")
    finally:
        # 清理资源
        joystick.quit()
        pygame.joystick.quit()
        pygame.quit()
    
    return 0

def main():
    """
    主函数
    """
    parser = argparse.ArgumentParser(description='游戏手柄测试工具')
    parser.add_argument('--device', type=int, default=0, help='游戏手柄设备ID (默认: 0)')
    parser.add_argument('--duration', type=int, help='测试持续时间（秒），不指定则无限时间')
    parser.add_argument('--mapping', help='按钮名称映射文件路径')
    parser.add_argument('--no-gui', action='store_true', help='禁用GUI显示')
    parser.add_argument('--verbose', '-v', action='store_true', help='显示详细日志')
    
    subparsers = parser.add_subparsers(dest='command', help='子命令')
    
    # 检测命令
    detect_parser = subparsers.add_parser('detect', help='检测已连接的游戏手柄')
    
    # 测试按钮命令
    test_parser = subparsers.add_parser('test', help='测试游戏手柄按钮')
    test_parser.add_argument('--timeout', type=int, default=30, help='测试超时时间（秒）(默认: 30)')
    test_parser.add_argument('--mapping', help='使用自定义按键映射文件')
    test_parser.add_argument('--output', help='保存按钮映射的输出文件路径')
    
    args = parser.parse_args()
    
    # 设置日志
    setup_logging(args.verbose)
    
    # 如果指定了命令
    if args.command == 'detect':
        detector = JoystickDetector()
        try:
            joysticks = detector.list_joysticks()
            if not joysticks:
                print("未检测到游戏手柄设备")
                return 1
            
            print(f"检测到 {len(joysticks)} 个游戏手柄设备:")
            for i, js in enumerate(joysticks):
                print(f"设备 {i}:")
                print(f"  名称: {js['name']}")
                print(f"  GUID: {js['guid']}")
                print(f"  按钮数量: {js['num_buttons']}")
                print(f"  轴数量: {js['num_axes']}")
                print(f"  HAT数量: {js['num_hats']}")
            
            return 0
        finally:
            detector.cleanup()
    
    elif args.command == 'test':
        detector = JoystickDetector()
        try:
            print(f"开始测试设备 {args.device} 的按钮")
            print("请依次按下手柄上的每个按钮...")
            
            # 将映射文件参数传递给测试函数
            button_map = detector.test_buttons(args.device, args.timeout, args.mapping)
            
            if not button_map:
                print("未检测到任何按钮按下")
                return 1
            
            print("\n按钮测试结果:")
            for button_id, button_name in sorted(button_map.items()):
                print(f"按钮 {button_id}: {button_name}")
            
            # 如果指定了输出文件，保存按钮映射
            if args.output:
                detector.create_button_mapping_file(button_map, args.output)
                print(f"按钮映射已保存到: {args.output}")
            
            return 0
        finally:
            detector.cleanup()
    
    else:
        # 如果禁用GUI
        if args.no_gui and pygame.display.get_init():
            pygame.display.quit()
        
        # 运行事件测试
        return test_joystick_events(args.device, args.duration, args.mapping)

if __name__ == '__main__':
    sys.exit(main()) 